import gevent
import pyrec
import json
import time
import random
import network

class Character (object):

    def __init__ (self):
        self.id = None
        self.body = None
        self.hp_cur = 100
        self.hp_max = 100
        self.position = 0, 0, 0

class GameServer (object):

    def __init__ (self):
        self.running = False
        self.elapsed_time = 0.0001
        self._connections = {}
        self._characters = {}
        self.scene_config = {}
        self.geomesh = pyrec.Geomesh ()
        self.navmesh = pyrec.Navmesh ()
        self.crowd = pyrec.Crowd ()

    def new_connection (self, socket, address):
        connection = network.Connection (socket, address)
        self.log ("--- new %s" % connection)
        self._connections [connection.id] = connection
        connection.handle ()
        self.log ("--- lost %s" % connection)

    def remove_connection (self, connection):
        self.log ("--- remove %s" % connection)
        return self._connections.pop (connection.id)

    def connections (self):
        return self._connections.values ()

    def identify_connection (self, connection, id):
        self.log ("--> identify %s, id:%s" % (connection, id))
        self.remove_connection (connection)
        connection.id = id
        self._connections [id] = connection
        message = "identify", id
        self.broadcast (message)
        self.log ("<-- %s" % connection)

    def broadcast (self, message):
        for connection in self.connections ():
            connection.send (message)

    def log (self, text):
        print (text)

    def start (self):
        self.log ("--- starting...")
        gevent.spawn (self.mainloop)

    def stop (self):
        self.log ("--- shutdown...")
        self.running = False

    def load_scene (self, name):
        self.log ("--> load scene: %s" % name)
        self.scene_config = json.load (open (name, "r"))
        config = self.scene_config
        self.log ("--- init geom")
        # self.geom = pirr.Geom ()
        self.geomesh.load (config ["geom"])
        self.log ("--- init navmesh")
        # self.navmesh = pirr.NavMesh ()
        for key in config ["navmesh"]:
            setattr (self.navmesh, key, config ["navmesh"][key])
        self.navmesh.build_from (self.geomesh)
        self.log ("--- init crowd")
        # self.crowd = pirr.Crowd ()
        for key in config ["crowd"]:
            setattr (self.crowd, key, config ["crowd"][key])
        self.crowd.init (self.navmesh)
        # self.crowd.create_body ((0,0,0))
        # self.crowd.update (0.1)
        self.log ("<-- done.")

    def mainloop (self):
        self.running = True
        while self.running:
            _seconds = time.time ()
            self.handle_connections ()
            self.handle_messages ()
            self.handle_characters (self.elapsed_time)
            gevent.sleep (0.05)
            self.elapsed_time = time.time () - _seconds
        self.close_connections ()

    def close_connections (self):
        self.log ("--- close connections")
        for connection in self.connections ():
            connection.close ()

    def handle_connections (self):
        for connection in self.connections ():
            if connection.lost:
                self.remove_connection (connection)
                # char

    def handle_messages (self):
        for connection in self.connections ():
            messages = connection.messages
            while messages:
                try:
                    message = messages.pop (0)
                    name = message [0]
                    args = message [1:]
                    method = self.__getattribute__ (name)
                    method (connection, *args)
                except Exception as e:
                    attrs = e, str(message), connection
                    self.log ("EEE %s {message:%s} %s" % attrs)

    def handle_characters (self, dt):
        crowd = self.crowd
        # WTF ERROR ?!!!
        # crashed if crowd.update (self.elapsed_time) o_O
        # WHY ???
        
        crowd.update (dt)
        for char in self.characters ():
            if char.body:
                vel = crowd.get_vel_of (char.body)
                dvel = crowd.get_dvel_of (char.body)
                nvel = crowd.get_nvel_of (char.body)
                pos = crowd.get_position_of (char.body)
                char.position = pos
                message = "char_motion", char.id, pos, vel, dvel, nvel
                self.broadcast (message)

    def login (self, connection, name):
        self.log ("--> login %s, name:%s" % (connection, name))
        if name in self.characters ():
            message = "login", "name_already_exist"
            connection.send (message)
            self.log ("??? name already exist")
            self.log ("<-- fail")
            return
        message = "login", "oK"
        connection.send (message)
        self.identify_connection (connection, name)
        characters = []
        for char in self.characters ():
            ch = char.id, char.position
            characters.append (ch)
        message = "load_scene", self.scene_config, characters
        connection.send (message)
        self.log ("--- create character")
        self.create_character (name)
        self.create_character (name + "_bot")
        self.log ("<-- success")

    def new_character (self, id):
        char = Character ()
        char.id = id
        self._characters [id] = char
        return char

    def characters (self):
        return self._characters.values ()

    def character (self, id):
        if id in self._characters:
            return self._characters [id]
        return None

    def create_character (self, name):
        self.log ("--> create character %s" % name)
        char = self.new_character (name)
        regions = self.scene_config ["regions"]
        num = len (regions.keys ())
        num = random.randint (0, num-1)
        begin, end = self.scene_config ["regions"]["start_%i"%num]
        x0, y0, z0 = begin
        x1, y1, z1 = end
        x = x0 + (x1 - x0) * random.random ()
        y = y0 + (y1 - y0) * random.random ()
        z = z0 + (z1 - z0) * random.random ()
        position = x, y, z
        char.body = self.crowd.create_body (position)
        attrs = char.__dict__
        message = "char_create", char.id, position, attrs
        self.broadcast (message)
        self.log ("<-- done.")

    def remove_character (self, char):
        self.log ("--> remove %s" % char)
        if char.id in self._characters:
            # char = self._characters.pop (char.id)
            self.crowd.remove_body (char.body)
        bot = char.id + "_bot"
        if bot in self._characters:
            # char = self._characters.pop (bot)
            self.crowd.remove_body (char.body)
        self.log ("<-- done.")

    def char_move_to (self, conn, char_id, position):
        char = self.character (char_id)
        if char is None:
            self.log ("!!! %s character is None" % conn)
            return
        self.crowd.move_body (char.body, position)